﻿using System;
using System.Collections.Generic;
using System.Globalization;
using OpenTap.Plugins.Interfaces.Common;
using OpenTap.Plugins.Interfaces.Eload;

namespace OpenTap.Plugins.Eload
{
    [Display("EloadKeysight3300", Group: "OpenTap.Plugins", Description: "Driver for Keysight 3300 electronic load")]
    public class EloadKeysight3300 : ScpiInstrument, IEload
    {
        private const uint MinChannelNumber = 1;
        private const double MinTimerMs = 0.008;
        private const double MaxTimerMs = 4000;
        private const double MaxTiggerDelayMs = 32;

        private const double MinPulseWidth = 0.05;
        private const double MaxPulseWidth = 4;


        #region Settings

        // ToDo: Add property here for each parameter the end user should be able to change
        private const uint MinTransientFreq = 1;
        private const uint MaxTransientFreq = 10000;
        private const uint MinTransientSlewRate = 1000;
        private const uint MaxTransientSlewRate = 5000000;
        protected Dictionary<ESlewRateDirection, string> SlewRateDirectDictionary = new Dictionary<ESlewRateDirection, string>
        {
            {ESlewRateDirection.Positive, "POS"},
            {ESlewRateDirection.Negative, "NEG"}
        };
        #endregion

        public EloadKeysight3300()
        {
            Name = "N3300";
        }

        /// <summary>
        /// Open procedure for the instrument.
        /// </summary>
        public override void Open()
        {
            base.Open();
            EloadReset();
        }

        public void Clear()
        {
            ScpiCommand("*CLS");
        }

        public EopcStatus Opc()
        {
            return ScpiQuery<int>("*OPC?").Equals(1) ? EopcStatus.Complete : EopcStatus.Incomplete;
        }

        public void Wait()
        {
            ScpiCommand("*WAI");
        }

        public void EloadReset()
        {
            ScpiCommand("*RST");
        }

        public string GetVersion() //
        {
            throw new NotImplementedException();
        }

        public void SetCurrent(long channel, double currentA)
        {
            if (channel < MinChannelNumber) throw new ArgumentOutOfRangeException(nameof(channel));
            if (currentA < 0) throw new ArgumentOutOfRangeException(nameof(currentA));
            SetChannel(channel);
            ScpiCommand("CURR " + currentA.ToString(CultureInfo.InvariantCulture));
        }

        public void SetResistance(long channel, double resistanceOhm)
        {
            if (channel < MinChannelNumber) throw new ArgumentOutOfRangeException(nameof(channel));
            if (resistanceOhm <= 0) throw new ArgumentOutOfRangeException(nameof(resistanceOhm));
            SetChannel(channel);
            ScpiCommand("RES " + resistanceOhm.ToString(CultureInfo.InvariantCulture));
        }

        public void ELoadOnOff(long channel, EState onOff)
        {
            if (channel < MinChannelNumber) throw new ArgumentOutOfRangeException(nameof(channel));
            SetChannel(channel);
            ScpiCommand("INPUT " + onOff);
        }

        public void SetMode(long channel, ELoadMode eLoadMode, double maxValueOfMeasurement)
        {
            if (channel < MinChannelNumber) throw new ArgumentOutOfRangeException(nameof(channel));
            SetChannel(channel);
            switch (eLoadMode)
            {
                case ELoadMode.ConstantCurrent:
                    ScpiCommand("FUNC CURR");
                    break;
                case ELoadMode.ConstantResistance:
                    ScpiCommand("FUNC RES");
                    break;
            }
        }

        public ELoadMode GetMode()
        {
            var emode = ELoadMode.EloadModeUnknown;
            var mode = ScpiQuery<string>("FUNC?").TrimEnd("\n".ToCharArray());
            if (mode == "CC" || mode == "CURR")
            {
                emode = ELoadMode.ConstantCurrent;
            }

            if (mode == "CR" || mode == "RES")
            {
                emode = ELoadMode.ConstantResistance;
            }

            return emode;
        }

        public void SetContinuousTransientModeOnOff(long channel, EState onOff)
        {
            if (channel < MinChannelNumber) throw new ArgumentOutOfRangeException(nameof(channel));
            SetChannel(channel);
            ScpiCommand("TRAN:MODE CONT");
            ScpiCommand("TRAN " + onOff);
        }

        public void SetupContinuousTransient(long channel, double mainCurrentA, double transientCurrentA,
            uint frequencyHz, uint dutyCyclePercentage)
        {
            if (GetMode() != ELoadMode.ConstantCurrent) throw new ApplicationException("eload in wrong mode, set it to ConstantCurrent mode!");
            SetCurrent(channel, mainCurrentA);
            SetTransientCurrent(channel, transientCurrentA);
            SetContinuousTransientFrequency(channel, frequencyHz);
            SetContinuousTransientDutyCycle(channel, dutyCyclePercentage);
        }

        public double MeasureCurrent(long channel)
        {
            if (channel < MinChannelNumber) throw new ArgumentOutOfRangeException(nameof(channel));
            SetChannel(channel);
            return ScpiQuery<double>("MEAS:CURR?");
        }

        public double MeasureVoltage(long channel)
        {
            if (channel < MinChannelNumber) throw new ArgumentOutOfRangeException(nameof(channel));
            SetChannel(channel);
            return ScpiQuery<double>("MEAS:VOLT?");
        }

        public double MeasurePower(long channel)
        {
            if (channel < MinChannelNumber) throw new ArgumentOutOfRangeException(nameof(channel));
            SetChannel(channel);
            return ScpiQuery<double>("MEAS:POW?");
        }

        public double MeasureMaximumVoltage(long channel)
        {
            if (channel < MinChannelNumber) throw new ArgumentOutOfRangeException(nameof(channel));
            SetChannel(channel);
            return ScpiQuery<double>("MEAS:VOLT:MAX?");
        }

        public double MeasureMinimumVoltage(long channel)
        {
            if (channel < MinChannelNumber) throw new ArgumentOutOfRangeException(nameof(channel));
            SetChannel(channel);
            return ScpiQuery<double>("MEAS:VOLT:MIN?");
        }

        public void SetChannel(long channel)
        {
            if (channel < MinChannelNumber) throw new ArgumentOutOfRangeException(nameof(channel));
            ScpiCommand("CHAN " + channel);
        }

        public long GetChannel()
        {
            return ScpiQuery<long>("CHAN?");
        }


        public void SetTransientCurrent(long channel, double transientCurrent)
        {
            SetChannel(channel);
            ScpiCommand("CURR:TLEV " + transientCurrent.ToString(CultureInfo.InvariantCulture) + ";SLEW MAX");
        }

        public void SetContinuousTransientFrequency(long channel, uint frequencyHz)
        {
            if (frequencyHz < MinTransientFreq) throw new ArgumentOutOfRangeException("frequencyHz");
            if (frequencyHz > MaxTransientFreq) throw new ArgumentOutOfRangeException("frequencyHz");

            SetChannel(channel);
            ScpiCommand("TRAN:FREQ " + frequencyHz);
        }

        public void SetContinuousTransientDutyCycle(long channel, uint dutyCyclePercentage)
        {
            //Spec says Duty cycle range: 3% to 97% (0.25 Hz - 1 kHz); 6% to 94% (1 kHz - 10 kHz)
            if (dutyCyclePercentage < 3 || dutyCyclePercentage > 97) throw new ArgumentOutOfRangeException("dutyCyclePercentage");
            SetChannel(channel);
            ScpiCommand("TRAN:DCYC " + dutyCyclePercentage);
        }

        public void SetTransientCurrentSlewRate(long channel, ESlewRateDirection slewRateDirect, double slewRate)
        {
            if (slewRate < MinTransientSlewRate) throw new ArgumentOutOfRangeException("slewRate");
            if (slewRate > MaxTransientSlewRate) throw new ArgumentOutOfRangeException("slewRate");
            SetChannel(channel);
            ScpiCommand("CURR:SLEW:" + SlewRateDirectDictionary[slewRateDirect] + " " + slewRate.ToString(CultureInfo.InvariantCulture));
        }

        public void SetTriggerSource(ETriggerSource source)
        {
            if (!Enum.IsDefined(typeof(ETriggerSource), source))
                throw new ArgumentOutOfRangeException("Source " + source + " not defined");

            if(source == ETriggerSource.Single)
                ScpiCommand("TRIG:IMM");
            else
                ScpiCommand("TRIG:SOUR " + source);
        }

        public void SetTriggerTimer(double timerMs)
        {
            if(timerMs < MinTimerMs || timerMs> MaxTimerMs)
                throw new ArgumentOutOfRangeException(nameof(timerMs));

            ScpiCommand("TRIG:TIM " + (timerMs/1000).ToString("###0.######"));
        }

        public void SetTriggerDelay(long channel, uint delayMs)
        {
            if (delayMs > MaxTiggerDelayMs)
                throw new ArgumentOutOfRangeException("Max trigger delay is" + MaxTimerMs);

            SetChannel(channel);
            ScpiCommand("TRIG:DEL " + (delayMs / 1000).ToString("0.###"));
        }

        public void ResetTriggerSystem()
        {
            ScpiCommand("ABOR");
        }

        public void InitiateTriggerSequence(ETriggerSequence triggerSeq)
        {
            ScpiCommand("INIT:NAME " + triggerSeq);
        }

        public void SetContinuousTriggerInitiateOnOff(EState state)
        {
            ScpiCommand("INIT:CONT:NAME LIST, " + state);
        }

        public void SelectTransientMode(long channel, ETransientMode mode)
        {
            SetChannel(channel);
            ScpiCommand("TRAN:MODE " + mode);
        }

        public void SetTransientModeOnOff(long channel, EState onOff)
        {
            SetChannel(channel);
            ScpiCommand("TRAN " + onOff);
        }

        public void SelectTransientSource(long channel, ETransientSource source)
        {
            SetChannel(channel);
            ScpiCommand("TRAN:LMODE " + source);
        }

        public void SetPulseTransientModeWidth(long channel, double pulseWidthMs)
        {
            if (pulseWidthMs < MinPulseWidth || pulseWidthMs > MaxPulseWidth)
                throw new ArgumentOutOfRangeException(nameof(channel));

            SetChannel(channel);
            ScpiCommand("TRAN:TWID " + (pulseWidthMs / 1000).ToString("###0.#####"));
        }

        public int SelfTest(out string message)
        {
            ScpiCommand("*CLS");
            var code = ScpiQuery<int>("*TST?");

            message = code == 0 ? "SelfTest passed." : ScpiQuery("SYST:ERR?");

            return code;
        }

        public void SetParallelInit(long channel, ELoadParallelInit onOff)
        {
            // NOTE: This method is not supported by Keysight 3300.
            throw new NotImplementedException();
        }

        public void SetParallelMode(long channel, ELoadParallelMode parallelMode)
        {
            // NOTE: This method is not supported by Keysight 3300.
            throw new NotImplementedException();
        }

        public ELoadParallelMode GetParallelMode()
        {
            // NOTE: This method is not supported by Keysight 3300.
            throw new NotImplementedException();
        }

        public void SetReadFromSense(long channel, bool sense)
        {
            throw new NotImplementedException();
        }
    }
}